/**
 * Basic storage unit to represent one Task 
 */

package storage;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;

import sharedUtils.Constants;
import sharedUtils.Logger;

public class Task {
	//@author A0097005J
	private static final String INVALID_TASK_MSG = "This Task is invalid.";
	private static final String NO_START_DATE_MSG = "No Start Date.";
	private static final String NO_END_DATE_MSG = "No End Date.";
	
	private static final String START_DATE_MSG_PREFIX = "Start Date is ";
	private static final String START_TIME_MSG_PREFIX = "Start Time is ";
	private static final String END_DATE_MSG_PREFIX = "End Date is ";
	private static final String END_TIME_MSG_PREFIX = "End Time is ";		
	
	private int id;
	private int priority;
	
	private GregorianCalendar startDate;
	private GregorianCalendar endDate;
	
	private boolean isStartDateSet;
	private boolean isStartTimeSet;
	private boolean isEndDateSet;
	private boolean isEndTimeSet;
	
	private String title;
	private String description;
	
	private boolean isValid;

	/**
	 * Default constructor. Creates a dummy placeholder task.
	 */
	public Task() {
		this.id = -1;
		this.priority = Constants.LOW_PRIORITY;
		
		this.startDate = null;
		this.endDate = null;
		
		this.title = INVALID_TASK_MSG;
		this.description = null;
		
		this.isValid = true;
		
		this.isStartDateSet = false;
		this.isStartTimeSet = false;
		this.isEndDateSet = false;
		this.isEndTimeSet = false;
	}
	
	/**
	 * Constructor to instantiate 1x Task. Note that title cannot be all 
	 * digits, and priority and id cannot be less than 1. Also note that, 
	 * by QuickTODO architecture policy, id should only be set by storage.TaskList.
	 * 
	 * @param sdate Task startDate
	 * @param edate Task endDate
	 * @param sdatevalid Task isStartDateValid
	 * @param stimevalid Task isStartTimeValid
	 * @param edatevalid Task isEndDateValid
	 * @param etimevalid Task isEndTimeValid
	 * @param title Task title
	 * @param description Task description
	 * @param priority Task priority
	 * @param id TaskId
	 */
	public Task(GregorianCalendar sdate, GregorianCalendar edate,
			boolean sdatevalid, boolean stimevalid,
			boolean edatevalid, boolean etimevalid,
			String title, String description,
			int priority, int id) {
		this.id = id;
		this.priority = priority;

		this.startDate = sdate;
		this.endDate = edate;
		
		this.title = title;
		this.description = description;
		
		this.priority = priority;
		
		this.isValid = true;
		
		this.isStartDateSet = sdatevalid;
		this.isStartTimeSet = stimevalid;
		this.isEndDateSet = edatevalid;
		this.isEndTimeSet = etimevalid;
	}
	
	/**
	 * Returns the Task startDate.
	 * 
	 * @return startDate
	 */
	public GregorianCalendar getStartDate() {
		return startDate;
	}
	
	/**
	 * Sets the Task startDate to the given GregorianCalendar.
	 * 
	 * @param startDate GregorianCalendar that represents the new startDate.
	 */
	public void setStartDate(GregorianCalendar startDate) {
		this.startDate = startDate;
	}
	
	/**
	 * Returns the Task endDate.
	 * 
	 * @return endDate
	 */
	public GregorianCalendar getEndDate() {
		return endDate;
	}
	
	/**
	 * sets the Task endDate to the given GregorianCalendar.
	 * 
	 * @param endDate GregorianCalendar that represents the new endDate.
	 */
	public void setEndDate(GregorianCalendar endDate) {
		this.endDate = endDate;
	}
	
	
	/**
	 * Returns the Task title.
	 * 
	 * @return title
	 */
	public String getTitle() {
		return title;
	}
	
	/**
	 * Sets the Task title to the given String.
	 * 
	 * @param title New Task title String.
	 */
	public void setTitle(String title) {
		this.title = title;
	}
	
	
	/**
	 * Returns the Task description.
	 * 
	 * @return description
	 */
	public String getDescription() {
		return description;
	}
	
	/**
	 * Sets the Task description to the given String.
	 * 
	 * @param description New Task description String.
	 */
	public void setDescription(String description) {
		this.description = description;
	}
	
	
	/**
	 * Returns the Task priority.
	 * 
	 * @return priority
	 */
	public int getPriority() {
		return priority;
	}
	
	/**
	 * Sets the Task priority to the given int.
	 * 
	 * @param priority New Task priority.
	 */
	public void setPriority(int priority) {
		this.priority = priority;
	}
	
	/**
	 * Returns the TaskId.
	 * 
	 * @return id
	 */
	public int getId() {
		return this.id;
	}
	
	/**
	 * Sets the TaskId to the given int. Note that only storage.TaskList 
	 * should be able to call this function.
	 * 
	 * @param id New TaskId
	 */
	protected void setId(int id) {
		this.id = id;
	}
	
	/**
	 * Returns if this Task is valid
	 * 
	 * @return isValid
	 */
	public boolean getIsValid() {
		return isValid;
	}
	
	/**
	 * Sets this Task's validity to the given boolean. Note that only 
	 * storage.TaskList should be able to call this function.
	 * 
	 * @param b New Task isValid boolean
	 */
	protected void setIsValid(boolean b) {
		this.isValid = b;
	}
	
	/**
	 * Returns if the Task's date in startDate is valid.
	 * 
	 * @return isStartDateSet
	 */
	public boolean isStartDateSet() {
		return isStartDateSet;
	}

	/**
	 * Sets the Task's isStartDateSet to the given boolean.
	 * 
	 * @param isStartDateSet New Task isStartDateSet boolean
	 */
	public void setStartDateSet(boolean isStartDateSet) {
		this.isStartDateSet = isStartDateSet;
	}

	/**
	 * Returns if the Task's time in startDate is valid.
	 * 
	 * @return isStartTimeSet
	 */
	public boolean isStartTimeSet() {
		return isStartTimeSet;
	}

	/**
	 * Sets the Task's isStartTimeSet to the given boolean.
	 * 
	 * @param isStartTimeSet New Task isStartTimeSet boolean
	 */
	public void setStartTimeSet(boolean isStartTimeSet) {
		this.isStartTimeSet = isStartTimeSet;
	}

	/**
	 * Returns if the Task's date in endDate is valid.
	 * 
	 * @return isEndDateSet
	 */
	public boolean isEndDateSet() {
		return isEndDateSet;
	}

	/**
	 * Sets the Task's isEndDateSet to the given boolean.
	 * 
	 * @param isEndDateSet New Task isEndDateSet boolean
	 */
	public void setEndDateSet(boolean isEndDateSet) {
		this.isEndDateSet = isEndDateSet;
	}

	/**
	 * Returns if the Task's time in endDate is valid.
	 * 
	 * @return isEndTimeSet
	 */
	public boolean isEndTimeSet() {
		return isEndTimeSet;
	}

	/**
	 * Sets the Task's isEndTimeSet to the given boolean.
	 * 
	 * @param isEndTimeSet New Task isEndTimeSet boolean
	 */
	public void setEndTimeSet(boolean isEndTimeSet) {
		this.isEndTimeSet = isEndTimeSet;
	}
	
	//@author A0097758E
	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return title;
	}
	
	/**
	 * Converts each field into a String and group them into an ArrayList and 
	 * returning it. Should only be used for debugging. Note that UI 
	 * components should perform the necessary formatting by individually 
	 * referencing the fields of the Task object and not by calling this method.
	 * 
	 * @return ArrayList of Strings, each element corresponding 
	 * to a field within this Task object in the following order: 
	 * 
	 * {[isValid], [taskId], [title], [description], [priority], 
	 * 		[startDate], [isStartDateSet], [isStartTimeSet], 
	 * 		[endDate], [isEndDateSet], [isEndTimeSet]}
	 */
	public ArrayList<String> toStringArrayListAllFields() {
		ArrayList<String> output = new ArrayList<String>();
		
		if(!isValid) {
			output.add(INVALID_TASK_MSG);
			return output;
		}
		
		output.add(Integer.toString(id));
		
		output.add(title);
		output.add(description);
		
		output.add(Integer.toString(priority));
		
		if(startDate != null) {
			output.add(startDate.toString());
		} else {
			output.add(NO_START_DATE_MSG);
		}
		
		if(isStartDateSet) {
			output.add(START_DATE_MSG_PREFIX + " valid.");
		} else {
			output.add(START_DATE_MSG_PREFIX + " invalid.");
		}
		
		if(isStartTimeSet) {
			output.add(START_TIME_MSG_PREFIX + " valid.");
		} else {
			output.add(START_TIME_MSG_PREFIX + " invalid.");
		}
		
		if(endDate != null) {
			output.add(endDate.toString());
		} else {
			output.add(NO_END_DATE_MSG);
		}
		
		if(isEndDateSet) {
			output.add(END_DATE_MSG_PREFIX + " valid.");
		} else {
			output.add(END_DATE_MSG_PREFIX + " invalid.");
		}
		
		if(isEndTimeSet) {
			output.add(END_TIME_MSG_PREFIX + " valid.");
		} else {
			output.add(END_TIME_MSG_PREFIX + " invalid.");
		}
		
		return output;
	}
	
	/**
	 * Clones and returns the argument Task. Avoid using Object.clone() 
	 * as it is an imperfect clone. Use this function instead.
	 * 
	 * @param t Task object to be cloned.
	 * @return Cloned Task object. Returns null on errors.
	 */
	public static Task createCopyFrom(Task t) {
		GregorianCalendar tempCal, tCal;
		
		if(t == null) {
			return null;
		}
		
		Logger.log("Cloning Task: " + t.getTitle(), 
				TaskList.class.getSimpleName(), "createCopyFrom()");
		
		Task output = new Task();
		
		tCal = t.getEndDate();
		if(tCal != null) {
			tempCal = new GregorianCalendar(tCal.get(Calendar.YEAR), 
					tCal.get(Calendar.MONTH), tCal.get(Calendar.DAY_OF_MONTH),
					tCal.get(Calendar.HOUR_OF_DAY), tCal.get(Calendar.MINUTE));
		} else {
			tempCal = null;
		}
		
		output.setEndDate(tempCal);
		output.setEndDateSet(t.isEndDateSet());
		output.setEndTimeSet(t.isEndTimeSet());

		tCal = t.getStartDate();
		if(tCal != null) {
			tempCal = new GregorianCalendar(tCal.get(Calendar.YEAR), 
					tCal.get(Calendar.MONTH), tCal.get(Calendar.DAY_OF_MONTH),
					tCal.get(Calendar.HOUR_OF_DAY), tCal.get(Calendar.MINUTE));
		} else {
			tempCal = null;
		}
		
		output.setStartDate(tempCal);
		output.setStartDateSet(t.isStartDateSet());
		output.setStartTimeSet(t.isStartTimeSet());
		
		output.setTitle(t.getTitle());
		output.setDescription(t.getDescription());
		
		output.setIsValid(t.getIsValid());
		
		output.setId(t.getId());
		output.setPriority(t.getPriority());
		
		return output;
	}

	/**
	 * Checks if the contents (not address pointers) of all fields in 
	 * Task 1 are equal to that of Task 2.
	 * 
	 * @param t1 Task 1
	 * @param t2 Task 2
	 * @return True if all contents of all fields are equal. False otherwise.
	 */
	public static boolean isContentsSameAs(Task t1, Task t2) {
		if(t1 == t2) {
			return true;
		}
		
		if((t1 == null && t2 != null) ||
			(t1 != null && t2 == null)) {
			return false;
		}
		
		Logger.log("Checking content equality on Task: " + t1.getTitle() + " and Task: " + t2.getTitle(), 
				TaskList.class.getSimpleName(), "isContentsSameAs()");
		
		// Check Task Id
		if(t1.getId() != t2.getId()) {
			return false;
		}
		
		//Check Title
		if((t1.getTitle() != null && t2.getTitle() == null) ||
			(t1.getTitle() == null && t2.getTitle() != null)) {
			return false;
		} else if(t1.getTitle() != null && t2.getTitle() != null &&
			!t1.getTitle().equals(t2.getTitle())) {
			return false;
		}
		
		//Check Description
		if((t1.getDescription() != null && t2.getDescription() == null) ||
			(t1.getDescription() == null && t2.getDescription() != null)) {
			return false;
		} else if(t1.getDescription() != null && t2.getDescription() != null &&
			!t1.getDescription().equals(t2.getDescription())) {
			return false;
		}
		
		//Check Priority
		if(t1.getPriority() != t2.getPriority()) {
			return false;
		}
		
		//Check Start Date
		if((t1.getStartDate() != null && t2.getStartDate() == null) ||
			(t1.getStartDate() == null && t2.getStartDate() != null)) {
			return false;
		} else if(t1.getStartDate() != null && t2.getStartDate() != null && (
				t1.getStartDate().get(Calendar.YEAR) != t2.getStartDate().get(Calendar.YEAR) ||
				t1.getStartDate().get(Calendar.MONTH) != t2.getStartDate().get(Calendar.MONTH) ||
				t1.getStartDate().get(Calendar.DAY_OF_MONTH) != t2.getStartDate().get(Calendar.DAY_OF_MONTH) ||
				t1.getStartDate().get(Calendar.HOUR_OF_DAY) != t2.getStartDate().get(Calendar.HOUR_OF_DAY) ||
				t1.getStartDate().get(Calendar.MINUTE) != t2.getStartDate().get(Calendar.MINUTE)
			)) {
			return false;
		}
		
		//Check Start Date isValid
		if(t1.isStartDateSet() != t2.isStartDateSet() ||
			t1.isStartTimeSet() != t2.isStartTimeSet()) {
			return false;
		}
		
		//Check End Date
		if((t1.getStartDate() != null && t2.getStartDate() == null) ||
			(t1.getStartDate() == null && t2.getStartDate() != null)) {
			return false;
		} else if(t1.getStartDate() != null && t2.getStartDate() != null && (
				t1.getStartDate().get(Calendar.YEAR) != t2.getStartDate().get(Calendar.YEAR) ||
				t1.getStartDate().get(Calendar.MONTH) != t2.getStartDate().get(Calendar.MONTH) ||
				t1.getStartDate().get(Calendar.DAY_OF_MONTH) != t2.getStartDate().get(Calendar.DAY_OF_MONTH) ||
				t1.getStartDate().get(Calendar.HOUR_OF_DAY) != t2.getStartDate().get(Calendar.HOUR_OF_DAY) ||
				t1.getStartDate().get(Calendar.MINUTE) != t2.getStartDate().get(Calendar.MINUTE)
			)) {
			return false;
		}
		
		//Check End Date isValid
		if(t1.isEndDateSet() != t2.isEndDateSet() ||
			t1.isEndTimeSet() != t2.isEndTimeSet()) {
			return false;
		}
		
		return true;
	}
}
